use std::collections::HashMap;

use anyhow::{Context, Result};
use encoding_rs::CoderResult;
use shared::{ChatSender, NodeId, TerminalInfo, TerminalPosition, TerminalSize};
use tokio::io::{AsyncReadExt, AsyncWriteExt};
use tokio::sync::mpsc;
use tracing::{debug, error, info, warn};

use crate::terminal::{get_default_shell, Terminal};

const BUFFER_SIZE: usize = 4096;
const CONTENT_ROLLING_BYTES: usize = 8 << 20; // 8MB

/// Terminal session management
pub struct TerminalSession {
    session_id: String,
    terminal: Terminal,
    sender: ChatSender,
    decoder: encoding_rs::Decoder,
    content: String,
    content_offset: usize,
    sequence: u64,
    last_activity: u64,
    current_size: (u16, u16),
}

impl TerminalSession {
    pub async fn new(
        session_id: String,
        _name: Option<String>, // name parameter kept for compatibility but not stored
        shell_path: Option<String>,
        sender: ChatSender,
    ) -> Result<Self> {
        let shell = match shell_path {
            Some(shell) => shell,
            None => get_default_shell().await,
        };
        info!(
            "Creating terminal session '{}' ({}) with shell: {}",
            session_id, session_id, shell
        );

        let mut terminal = Terminal::new(&shell)
            .await
            .context("Failed to create terminal")?;

        // Set initial terminal size
        terminal
            .set_winsize(24, 80)
            .context("Failed to set terminal size")?;

        // Get actual terminal size
        let size = terminal.get_winsize().unwrap_or((24, 80));

        let now = std::time::SystemTime::now()
            .duration_since(std::time::UNIX_EPOCH)
            .unwrap()
            .as_secs();

        Ok(Self {
            session_id,
            terminal,
            sender,
            decoder: encoding_rs::UTF_8.new_decoder(),
            content: String::new(),
            content_offset: 0,
            sequence: 0,
            last_activity: now,
            current_size: size,
        })
    }

    pub async fn run(mut self, mut command_rx: mpsc::Receiver<TerminalCommand>) -> Result<()> {
        let mut buf = [0u8; BUFFER_SIZE];
        let mut finished = false;

        // Notify that terminal is ready
        self.sender
            .send_terminal_status(self.session_id.clone(), "ready".to_string())
            .await?;

        while !finished {
            tokio::select! {
                // Read from terminal
                result = self.terminal.read(&mut buf) => {
                    match result {
                        Ok(0) => {
                            // Terminal closed
                            finished = true;
                            self.sender.send_terminal_status(
                                self.session_id.clone(),
                                "closed".to_string()
                            ).await?;
                        }
                        Ok(n) => {
                            // Process terminal output
                            self.process_terminal_output(&buf[..n]).await?;
                        }
                        Err(e) => {
                            error!("Terminal read error: {}", e);
                            self.sender.send_terminal_status(
                                self.session_id.clone(),
                                format!("error: {}", e)
                            ).await?;
                            finished = true;
                        }
                    }
                }

                // Handle commands
                Some(command) = command_rx.recv() => {
                    match command {
                        TerminalCommand::Input(data) => {
                            if let Err(e) = self.terminal.write_all(&data).await {
                                error!("Failed to write to terminal: {}", e);
                                self.sender.send_terminal_status(
                                    self.session_id.clone(),
                                    format!("write_error: {}", e)
                                ).await?;
                            }
                        }
                        TerminalCommand::Resize(rows, cols) => {
                            if let Err(e) = self.terminal.set_winsize(rows, cols) {
                                error!("Failed to resize terminal: {}", e);
                            } else {
                                debug!("Resized terminal {} to {}x{}", self.session_id, rows, cols);
                                self.current_size = (rows, cols);
                            }
                        }
                        TerminalCommand::Close => {
                            info!("Closing terminal session {}", self.session_id);
                            finished = true;
                        }
                        TerminalCommand::Rename(new_name) => {
                            info!("Renaming terminal session '{}' to '{:?}'", self.session_id, new_name);
                            // Update local name
                            // Note: In a real implementation, you might want to send a rename event
                            // For now, we just update the local state
                            // The actual rename event is sent by the ChatSender
                        }
                    }
                }

                // Command channel closed
                else => {
                    info!("Command channel closed for terminal {}", self.session_id);
                    finished = true;
                }
            }
        }

        // Send final status
        self.sender
            .send_terminal_status(self.session_id.clone(), "terminated".to_string())
            .await?;

        info!("Terminal session {} ended", self.session_id);
        Ok(())
    }

    async fn process_terminal_output(&mut self, data: &[u8]) -> Result<()> {
        // Update last activity time
        self.last_activity = std::time::SystemTime::now()
            .duration_since(std::time::UNIX_EPOCH)
            .unwrap()
            .as_secs();

        // Decode UTF-8
        self.content
            .reserve(self.decoder.max_utf8_buffer_length(data.len()).unwrap_or(0));
        let (result, _, _) = self
            .decoder
            .decode_to_string(data, &mut self.content, false);

        if result != CoderResult::InputEmpty {
            warn!("UTF-8 decoding error in terminal output");
        }

        // Send output if we have new data
        if (self.content_offset + self.content.len()) as u64 > self.sequence {
            let start =
                self.prev_char_boundary((self.sequence - self.content_offset as u64) as usize);
            let end = self.prev_char_boundary((start + BUFFER_SIZE).min(self.content.len()));

            if start < end {
                let output_data = self.content.as_bytes()[start..end].to_vec();
                self.sender
                    .send_terminal_output(self.session_id.clone(), output_data)
                    .await?;

                self.sequence = (self.content_offset + end) as u64;
            }
        }

        // Prune old content to prevent memory growth
        if self.content.len() > CONTENT_ROLLING_BYTES
            && self.sequence > (self.content_offset + CONTENT_ROLLING_BYTES) as u64
        {
            let prune_amount =
                self.sequence - self.content_offset as u64 - CONTENT_ROLLING_BYTES as u64;
            let pruned = self.prev_char_boundary(prune_amount as usize);
            self.content.drain(..pruned);
            self.content_offset += pruned;
        }

        Ok(())
    }

    fn prev_char_boundary(&self, index: usize) -> usize {
        (0..=index)
            .rev()
            .find(|&i| self.content.is_char_boundary(i))
            .unwrap_or(0)
    }
}

/// Commands that can be sent to a terminal session
pub enum TerminalCommand {
    Input(Vec<u8>),
    Resize(u16, u16),
    Close,
    Rename(Option<String>), // Add rename command
}

/// Manager for multiple terminal sessions
pub struct TerminalManager {
    sessions: HashMap<String, TerminalSessionInfo>, // session_id -> session info
}

/// Information about a terminal session
#[derive(Clone)]
struct TerminalSessionInfo {
    sender: mpsc::Sender<TerminalCommand>,
    name: Option<String>,
    created_at: u64,
    last_activity: u64,
    size: (u16, u16),
}

impl TerminalManager {
    pub fn new(_node_id: NodeId) -> Self {
        Self {
            sessions: HashMap::new(),
        }
    }

    // Optimized state synchronization with minimal network overhead
    pub async fn sync_state_optimized(&self, sender: ChatSender) -> Result<()> {
        let terminal_infos = self.get_terminal_infos();

        // Only send state if we have terminals
        if !terminal_infos.is_empty() {
            sender.send_terminal_state(terminal_infos).await?;
        }

        Ok(())
    }

    pub async fn create_session(
        &mut self,
        session_id: String,
        name: Option<String>,
        shell_path: Option<String>,
        sender: ChatSender,
    ) -> Result<()> {
        if self.sessions.contains_key(&session_id) {
            return Err(anyhow::anyhow!("Session {} already exists", session_id));
        }

        let (command_tx, command_rx) = mpsc::channel(32);
        let now = std::time::SystemTime::now()
            .duration_since(std::time::UNIX_EPOCH)
            .unwrap()
            .as_secs();

        let session =
            TerminalSession::new(session_id.clone(), name.clone(), shell_path, sender.clone())
                .await?;

        // Spawn session task
        let session_id_clone = session_id.clone();
        tokio::spawn(async move {
            if let Err(e) = session.run(command_rx).await {
                error!("Terminal session {} failed: {}", session_id_clone, e);
            }
        });

        self.sessions.insert(
            session_id.clone(),
            TerminalSessionInfo {
                sender: command_tx,
                name: name.clone(),
                created_at: now,
                last_activity: now,
                size: (24, 80), // Default size
            },
        );

        let display_name = name.as_deref().unwrap_or(&session_id);
        info!("Created new terminal session: '{}'", display_name);
        Ok(())
    }

    // Helper method to resolve session identifier (session_id or name)
    // Returns all matching session_ids (supports duplicate names)
    fn resolve_session_ids(&self, identifier: &str) -> Vec<String> {
        let mut results = Vec::new();

        // First try as session_id
        if self.sessions.contains_key(identifier) {
            results.push(identifier.to_string());
        }

        // Then try as name (find all terminals with this name)
        for (session_id, session_info) in &self.sessions {
            if session_info.name.as_deref() == Some(identifier) {
                results.push(session_id.clone());
            }
        }

        results
    }

    // Helper method to resolve single session identifier (for operations that expect uniqueness)
    fn resolve_session_id(&self, identifier: &str) -> Option<String> {
        let ids = self.resolve_session_ids(identifier);
        ids.into_iter().next()
    }

    pub async fn send_input(&self, identifier: &str, data: Vec<u8>) -> Result<()> {
        let session_id = self
            .resolve_session_id(identifier)
            .ok_or_else(|| anyhow::anyhow!("Terminal '{}' not found", identifier))?;

        if let Some(session_info) = self.sessions.get(&session_id) {
            session_info
                .sender
                .send(TerminalCommand::Input(data))
                .await
                .context("Failed to send input to terminal")?;
        } else {
            return Err(anyhow::anyhow!("Session {} not found", session_id));
        }
        Ok(())
    }

    pub async fn resize_terminal(&self, identifier: &str, rows: u16, cols: u16) -> Result<()> {
        let session_id = self
            .resolve_session_id(identifier)
            .ok_or_else(|| anyhow::anyhow!("Terminal '{}' not found", identifier))?;

        if let Some(session_info) = self.sessions.get(&session_id) {
            session_info
                .sender
                .send(TerminalCommand::Resize(rows, cols))
                .await
                .context("Failed to send resize command")?;
        } else {
            return Err(anyhow::anyhow!("Session {} not found", session_id));
        }
        Ok(())
    }

    pub async fn close_session(&mut self, identifier: &str) -> Result<()> {
        let session_id = self
            .resolve_session_id(identifier)
            .ok_or_else(|| anyhow::anyhow!("Terminal '{}' not found", identifier))?;

        if let Some(session_info) = self.sessions.remove(&session_id) {
            session_info
                .sender
                .send(TerminalCommand::Close)
                .await
                .context("Failed to send close command")?;

            info!("Closed terminal session: {}", identifier);
        } else {
            return Err(anyhow::anyhow!("Session {} not found", session_id));
        }
        Ok(())
    }

    pub fn list_sessions(&self) -> Vec<String> {
        self.sessions.keys().cloned().collect()
    }

    pub async fn rename_session(
        &mut self,
        identifier: &str,
        new_name: Option<String>,
    ) -> Result<()> {
        let session_id = self
            .resolve_session_id(identifier)
            .ok_or_else(|| anyhow::anyhow!("Terminal '{}' not found", identifier))?;

        if let Some(session_info) = self.sessions.get(&session_id) {
            // Send rename command to terminal session
            session_info
                .sender
                .send(TerminalCommand::Rename(new_name.clone()))
                .await
                .context("Failed to send rename command")?;

            // Update local state
            let mut updated_info = session_info.clone();
            updated_info.name = new_name.clone();
            self.sessions.insert(session_id.clone(), updated_info);

            let display_name = new_name.as_deref().unwrap_or("no name");
            info!(
                "Renamed terminal session '{}' to '{}'",
                identifier, display_name
            );
        } else {
            return Err(anyhow::anyhow!("Session {} not found", session_id));
        }
        Ok(())
    }

    pub fn get_terminal_infos(&self) -> Vec<TerminalInfo> {
        self.sessions
            .iter()
            .map(|(session_id, session_info)| TerminalInfo {
                session_id: session_id.clone(),
                name: session_info.name.clone(),
                position: TerminalPosition { x: 0, y: 0 },
                size: TerminalSize {
                    rows: session_info.size.0 as u32,
                    cols: session_info.size.1 as u32,
                },
                status: "active".to_string(),
                created_at: session_info.created_at,
                last_activity: session_info.last_activity,
            })
            .collect()
    }

    pub async fn close_all_sessions(&mut self) -> Result<()> {
        let sessions: Vec<String> = self.sessions.keys().cloned().collect();
        for session_id in sessions {
            if let Err(e) = self.close_session(&session_id).await {
                error!("Failed to close session {}: {}", session_id, e);
            }
        }
        Ok(())
    }
}

impl Drop for TerminalManager {
    fn drop(&mut self) {
        // Note: This is a sync drop method, but we need to be async
        // In practice, sessions should be explicitly closed before drop
        if !self.sessions.is_empty() {
            warn!(
                "TerminalManager dropped with {} active sessions",
                self.sessions.len()
            );
        }
    }
}
